//+------------------------------------------------------------------+
//|                                              ExecutionEngine.mqh |
//|                                    Copyright 2024, Precision EA  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Precision EA"
#property strict

#include "Logger.mqh"
#include <Trade/Trade.mqh>

enum ENUM_EXECUTION_STATUS {
    EXECUTION_PENDING,
    EXECUTION_SUCCESS,
    EXECUTION_PARTIAL,
    EXECUTION_FAILED,
    EXECUTION_REJECTED
};

struct ExecutionResult {
    ENUM_EXECUTION_STATUS status;
    ulong ticket;
    double executed_price;
    double executed_volume;
    datetime execution_time;
    string error_message;
    int error_code;
    double sl_price;
    double tp_price;
};

class ExecutionEngine {
private:
    string m_symbol;
    double m_point;
    int m_digits;
    double m_tick_size;
    CTrade m_trade;
    
    // Configuration
    double m_slippage_points;
    int m_max_retries;
    int m_retry_delay_ms;
    bool m_validate_spread;
    double m_max_spread_points;
    
    // Cache
    double m_last_ask;
    double m_last_bid;
    datetime m_last_tick_time;
    
    void RefreshMarketData() {
        m_last_tick_time = TimeCurrent();
        
        MqlTick tick;
        if(SymbolInfoTick(m_symbol, tick)) {
            m_last_ask = tick.ask;
            m_last_bid = tick.bid;
        } else {
            int error = GetLastError();
            Logger::Error("Failed to get tick data", "ExecutionEngine", error);
            RefreshRates();
            m_last_ask = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
            m_last_bid = SymbolInfoDouble(m_symbol, SYMBOL_BID);
        }
    }
    
    double NormalizePrice(double price) const {
        if(price <= 0) {
            Logger::Error(StringFormat("Invalid price: %.5f", price), "ExecutionEngine");
            return 0.0;
        }
        
        if(m_tick_size > 0) {
            return MathRound(price / m_tick_size) * m_tick_size;
        }
        
        return NormalizeDouble(price, m_digits);
    }
    
    double NormalizeVolume(double volume) const {
        double min_lot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);
        double max_lot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX);
        double lot_step = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP);
        
        if(lot_step <= 0) return volume;
        
        double normalized = MathRound(volume / lot_step) * lot_step;
        normalized = MathMax(normalized, min_lot);
        normalized = MathMin(normalized, max_lot);
        
        return normalized;
    }
    
    double GetMinStopDistance() const {
        // Get broker-specific stop levels
        int stop_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
        int freeze_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_FREEZE_LEVEL);
        
        double min_distance = MathMax(stop_level, freeze_level) * m_point;
        
        // Absolute minimum for safety
        if(min_distance < 10 * m_point) {
            min_distance = 10 * m_point;
        }
        
        // Special handling for volatile symbols
        if(StringFind(m_symbol, "XAU") >= 0 || StringFind(m_symbol, "BTC") >= 0) {
            min_distance = MathMax(min_distance, 100 * m_point);
        }
        
        return min_distance;
    }
    
    bool ValidateSpread() {
        if(!m_validate_spread) return true;
        
        if(m_last_ask <= 0 || m_last_bid <= 0) {
            Logger::Error("Invalid market prices", "ExecutionEngine");
            return false;
        }
        
        double spread = (m_last_ask - m_last_bid) / m_point;
        
        if(spread > m_max_spread_points) {
            Logger::Warn(StringFormat("Spread too high: %.1f > %.1f", 
                spread, m_max_spread_points), "ExecutionEngine");
            return false;
        }
        
        // Emergency stop for extreme spreads
        if(spread > 500) {
            Logger::Critical("Extreme spread detected", "ExecutionEngine");
            return false;
        }
        
        return true;
    }
    
    bool ValidatePriceLevels(double entry, double sl, double tp, ENUM_ORDER_TYPE type) {
        double min_distance = GetMinStopDistance();
        
        // Validate Stop Loss
        if(sl > 0) {
            double sl_distance = MathAbs(entry - sl);
            
            if(sl_distance < min_distance) {
                Logger::Error(StringFormat("SL too close: %.1f < %.1f", 
                    sl_distance/m_point, min_distance/m_point), "ExecutionEngine");
                return false;
            }
            
            // Check direction
            if((type == ORDER_TYPE_BUY && sl >= entry) ||
               (type == ORDER_TYPE_SELL && sl <= entry)) {
                Logger::Error("SL on wrong side of entry", "ExecutionEngine");
                return false;
            }
        }
        
        // Validate Take Profit
        if(tp > 0) {
            double tp_distance = MathAbs(entry - tp);
            
            if(tp_distance < min_distance) {
                Logger::Error(StringFormat("TP too close: %.1f < %.1f", 
                    tp_distance/m_point, min_distance/m_point), "ExecutionEngine");
                return false;
            }
            
            // Check direction
            if((type == ORDER_TYPE_BUY && tp <= entry) ||
               (type == ORDER_TYPE_SELL && tp >= entry)) {
                Logger::Error("TP on wrong side of entry", "ExecutionEngine");
                return false;
            }
        }
        
        // Validate SL/TP ratio (minimum 1:1)
        if(sl > 0 && tp > 0) {
            double risk = MathAbs(entry - sl);
            double reward = MathAbs(entry - tp);
            
            if(reward < risk) {
                Logger::Warn(StringFormat("Risk/Reward ratio < 1: %.2f", reward/risk), "ExecutionEngine");
            }
        }
        
        return true;
    }
    
    bool IsPriceValid(double price, ENUM_ORDER_TYPE type) {
        if(price <= 0) {
            Logger::Error("Price <= 0", "ExecutionEngine");
            return false;
        }
        
        // Check if price is within reasonable range of current market
        double current_price = (type == ORDER_TYPE_BUY) ? m_last_ask : m_last_bid;
        double diff_percent = MathAbs(price - current_price) / current_price * 100;
        
        if(diff_percent > 5.0) { // More than 5% away
            Logger::Error(StringFormat("Price too far from market: %.2f%%", diff_percent), "ExecutionEngine");
            return false;
        }
        
        return true;
    }

public:
    ExecutionEngine(string symbol, 
                   double slippage = 3.0,
                   int max_retries = 3,
                   int retry_delay = 100,
                   bool validate_spread = true,
                   double max_spread = 30.0) {
        m_symbol = symbol;
        m_slippage_points = slippage;
        m_max_retries = max_retries;
        m_retry_delay_ms = retry_delay;
        m_validate_spread = validate_spread;
        m_max_spread_points = max_spread;
        
        m_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
        m_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
        m_tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
        
        if(m_tick_size <= 0) {
            m_tick_size = MathPow(10, -m_digits);
        }
        
        m_trade.SetDeviationInPoints((uint)slippage);
        m_trade.SetTypeFilling(ORDER_FILLING_IOC);
        
        Logger::Info(StringFormat("Initialized for %s (Point: %.5f, Digits: %d)", 
            symbol, m_point, m_digits), "ExecutionEngine");
    }
    
    ExecutionResult ExecuteOrder(ENUM_ORDER_TYPE type,
                                double volume,
                                double price = 0,
                                double sl = 0,
                                double tp = 0,
                                string comment = "",
                                datetime expiration = 0) {
        ExecutionResult result = {};
        result.status = EXECUTION_PENDING;
        
        // Refresh market data
        RefreshMarketData();
        
        // Validate inputs
        volume = NormalizeVolume(volume);
        if(volume <= 0) {
            result.status = EXECUTION_REJECTED;
            result.error_message = "Invalid volume";
            return result;
        }
        
        // Set entry price if not provided
        if(price == 0) {
            price = (type == ORDER_TYPE_BUY) ? m_last_ask : m_last_bid;
        }
        
        // Normalize all prices
        price = NormalizePrice(price);
        sl = (sl > 0) ? NormalizePrice(sl) : 0;
        tp = (tp > 0) ? NormalizePrice(tp) : 0;
        
        // Validations
        if(!IsPriceValid(price, type)) {
            result.status = EXECUTION_REJECTED;
            result.error_message = "Invalid price";
            return result;
        }
        
        if(!ValidateSpread()) {
            result.status = EXECUTION_REJECTED;
            result.error_message = "Spread validation failed";
            return result;
        }
        
        if(!ValidatePriceLevels(price, sl, tp, type)) {
            result.status = EXECUTION_REJECTED;
            result.error_message = "Price levels validation failed";
            return result;
        }
        
        // Try execution with retries
        for(int attempt = 0; attempt <= m_max_retries; attempt++) {
            if(attempt > 0) {
                Logger::Warn(StringFormat("Retry %d/%d", attempt, m_max_retries), "ExecutionEngine");
                Sleep(m_retry_delay_ms);
                RefreshMarketData();
                
                // Update price for market orders
                if(price == 0) {
                    price = (type == ORDER_TYPE_BUY) ? m_last_ask : m_last_bid;
                    price = NormalizePrice(price);
                }
            }
            
            bool success = false;
            
            if(type == ORDER_TYPE_BUY || type == ORDER_TYPE_SELL) {
                // Market order
                success = m_trade.PositionOpen(m_symbol, type, volume, price, sl, tp, comment);
            } else {
                // Pending order
                success = m_trade.OrderOpen(m_symbol, type, volume, price, 0, sl, tp, expiration, comment);
            }
            
            if(success) {
                result.status = EXECUTION_SUCCESS;
                result.ticket = m_trade.ResultOrder();
                result.executed_price = m_trade.ResultPrice();
                result.executed_volume = m_trade.ResultVolume();
                result.execution_time = TimeCurrent();
                result.sl_price = sl;
                result.tp_price = tp;
                
                Logger::Info(StringFormat("Order executed: %s %.2f @ %.5f (Ticket: %d)",
                    EnumToString(type), volume, price, result.ticket), "ExecutionEngine");
                break;
            } else {
                result.error_code = m_trade.ResultRetcode();
                result.error_message = m_trade.ResultComment();
                
                if(attempt == m_max_retries) {
                    result.status = EXECUTION_FAILED;
                    Logger::Error(StringFormat("Order failed after %d attempts: %s",
                        m_max_retries, result.error_message), "ExecutionEngine", result.error_code);
                }
            }
        }
        
        return result;
    }
    
    bool ModifyPosition(ulong ticket, double new_sl = 0, double new_tp = 0, double new_price = 0) {
        if(!PositionSelectByTicket(ticket)) {
            Logger::Error(StringFormat("Position not found: %d", ticket), "ExecutionEngine");
            return false;
        }
        
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
        double current_price = PositionGetDouble(POSITION_PRICE_OPEN);
        
        // Normalize new prices
        new_sl = (new_sl > 0) ? NormalizePrice(new_sl) : 0;
        new_tp = (new_tp > 0) ? NormalizePrice(new_tp) : 0;
        new_price = (new_price > 0) ? NormalizePrice(new_price) : current_price;
        
        // Validate new levels
        if(!ValidatePriceLevels(new_price, new_sl, new_tp, 
           (type == POSITION_TYPE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL)) {
            return false;
        }
        
        if(m_trade.PositionModify(ticket, new_sl, new_tp)) {
            Logger::Info(StringFormat("Position modified: %d SL=%.5f TP=%.5f", 
                ticket, new_sl, new_tp), "ExecutionEngine");
            return true;
        }
        
        int error = GetLastError();
        Logger::Error(StringFormat("Failed to modify position %d", ticket), "ExecutionEngine", error);
        return false;
    }
    
    bool ClosePosition(ulong ticket, double volume = 0) {
        if(!PositionSelectByTicket(ticket)) {
            Logger::Error(StringFormat("Position not found: %d", ticket), "ExecutionEngine");
            return false;
        }
        
        if(volume <= 0) {
            volume = PositionGetDouble(POSITION_VOLUME);
        }
        
        if(m_trade.PositionClose(ticket, volume)) {
            Logger::Info(StringFormat("Position closed: %d", ticket), "ExecutionEngine");
            return true;
        }
        
        int error = GetLastError();
        Logger::Error(StringFormat("Failed to close position %d", ticket), "ExecutionEngine", error);
        return false;
    }
    
    void CloseAllPositions() {
        int total = PositionsTotal();
        for(int i = total - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if(ticket > 0) {
                ClosePosition(ticket);
            }
        }
        Logger::Info("All positions closed", "ExecutionEngine");
    }
    
    double GetCurrentSpread() const {
        return (m_last_ask - m_last_bid) / m_point;
    }
    
    double GetLastAsk() const { return m_last_ask; }
    double GetLastBid() const { return m_last_bid; }
};